package com.gitee.wsl.common.draw.zdog.sample

import android.animation.Keyframe
import android.animation.TypeEvaluator
import android.graphics.*
import android.view.animation.OvershootInterpolator
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import com.gitee.wsl.android.ext.base.color
import com.gitee.wsl.common.draw.zdog.Zdog.vector
import com.gitee.wsl.common.draw.zdog.bean.ShaderLayer
import com.gitee.wsl.common.draw.zdog.Zdog
import com.gitee.wsl.common.draw.zdog.ZdogDrawable
import com.gitee.wsl.common.draw.zdog.render.shape.Shape
import com.gitee.wsl.common.draw.zdog.render.TAU
import com.gitee.wsl.common.draw.zdog.animate
import com.gitee.wsl.android.ui.ext.duration
import com.gitee.wsl.android.ui.ext.interpolator
import com.gitee.wsl.android.ui.ext.toReverse
import kotlin.math.roundToInt

/**
 * Zdog 示例
 */
class SampleShape {

    companion object {
        fun zdog1() = ZdogDrawable().apply {
            illo.alpha(0f)
            val line = Zdog.shape {
                addTo = illo

                path {
                    moveTo(x = -32f, y = -40f)
                    lineTo(x = 32f, y = -40f)
                    lineTo(x = -32f, y = 40f)
                    lineTo(x = 32f, y = 40f)
                }

                closed = false
                stroke = 20f
                color = "#636"
                updateSegment(0f, 0f)
            }

            play(line.animate {
                duration = 1500
                interpolator = FastOutSlowInInterpolator()

                onReset {
                    line.updateSegment(0f, 1f)
                }

                update {
                    line.updateSegment(0f, it)
                }
                toReverse()
            })
        }

        fun zdog2() = ZdogDrawable().apply {
            illo.alpha(0f)
            shape {
                addTo = illo

                path {
                    moveTo(-90f)
                    lineTo(-90f)
                }

                color = "#FD4"
                stroke = 16f
            }

            shape {
                addTo = illo

                path {
                    moveTo(90f)
                    lineTo(90f)
                }

                color = "#FD4"
                stroke = 16f
            }

            shape {
                addTo = illo

                path {
                    moveTo(-90f, 0f)
                    arc(
                        vector(-90f, -90f),
                        vector(0f, -90f)
                    )
                    arc(
                        vector(90f, -90f),
                        vector(90f, 0f)
                    )
                }

                translate { z = -8f }
                color = "#636"
                effect = DashPathEffect(floatArrayOf(20f, 10f), 0f)
                stroke = 4f
                closed = false
            }

            illo.rotate { z = -(TAU / 8).toFloat() }

            play(
                illo.rotateBy(z = (TAU / 4).toFloat()).duration(1500)
                    .interpolator(OvershootInterpolator()).toReverse()
            )
        }

        fun zdog3() = ZdogDrawable().apply {
            illo.alpha(0f)

            shape {
                addTo = illo
                path {
                    moveTo(-90f, 0f)
                    arc(
                        vector(-90f, 90f),
                        vector(0f, 90f)
                    )
                    arc(
                        vector(90f, 90f),
                        vector(90f, 0f)
                    )
                }

                shader = LinearGradient(
                    0f, 90f, 0f, 0f,
                    "#636".color, Color.TRANSPARENT, Shader.TileMode.CLAMP
                )
                fill = true
                stroke = 0f
                closed = false
            }

            illo.rotate { z = (TAU / 8).toFloat() }
            play(
                illo.rotateBy(z = -(TAU / 4).toFloat()).duration(1500)
                    .interpolator(OvershootInterpolator()).toReverse()
            )
        }

        fun zdog4() = ZdogDrawable().apply {
            illo.alpha(0f)

            shape {
                addTo = illo

                path {
                    moveTo(-90f, 0f)
                    arc(
                        vector(-90f, 90f),
                        vector(0f, 90f)
                    )
                    arc(
                        vector(90f, 90f),
                        vector(90f, 0f)
                    )
                }

                color = "#fff"
                layer = ShaderLayer(
                    16f, 0f, 0f,
                    Colors.shader.colour
                )
                stroke = 8f
                closed = false
            }

            illo.rotate { z = (TAU / 8).toFloat() }
            play(
                illo.rotateBy(z = -(TAU / 4).toFloat()).duration(1500)
                    .interpolator(OvershootInterpolator()).toReverse()
            )
        }

        fun zdog5() = ZdogDrawable().apply {
            illo.alpha(0f)
            val dotted = shape {
                addTo = illo


                path {
                    moveTo(-90f, 0f)
                    arc(
                        vector(-90f, -90f),
                        vector(0f, -90f)
                    )
                    arc(
                        vector(90f, -90f),
                        vector(90f, 0f)
                    )
                }

                translate { z = -8f }
                color = "#636"
                effect = DashPathEffect(floatArrayOf(20f, 10f), 0f)
                stroke = 4f
                closed = false
            }

            val keyframes = PathKeyframes(illo.renderToPath(dotted))
            val xFrames = keyframes.createXFloatKeyframes()
            val yFrames = keyframes.createYFloatKeyframes()

            val dot = shape {
                addTo = illo
                color = "#FD4"
                stroke = 16f

                translate {
                    x = xFrames.getFloatValue(0f)
                    y = yFrames.getFloatValue(0f)
                }
            }

            play(
                dot.animate {
                    duration=1500
                    interpolator=FastOutSlowInInterpolator()
                    onReset {
                        dot.translate {
                            x = xFrames.getFloatValue(0f)
                            y = yFrames.getFloatValue(0f)
                        }
                    }

                    update {
                        dot.translate {
                            x = xFrames.getFloatValue(it)
                            y = yFrames.getFloatValue(it)
                        }
                    }
                    toReverse()
                }
            )
        }

        fun zdog6() = ZdogDrawable().apply {
            illo.alpha(0f)
            val arrow = shape {
                addTo = illo

                path {
                    moveTo(-80f, 40f)
                    lineTo(0f, -40f)
                    lineTo(80f, 40f)
                }

                color = "#fff"
                stroke = 10f
                layer = ShaderLayer(
                    16f, 0f, 0f,
                    Colors.shader.colour
                )
                closed = false
            }

            fun updatePath(shape: Shape, top: Float) {
                shape.apply {
                    path[0].point().y = -top
                    path[1].point().y = top
                    path[2].point().y = -top
                }
            }

            play(arrow.animate {
                duration = 1500

                onReset {
                    updatePath(arrow, 40f)
                }

                update {
                    updatePath(arrow, -40f + it * 80f)
                }

                toReverse()
            })
        }
    }
}

enum class Colors(val color: String) {
    midnight("#313"),
    eggplant("#525"),
    magenta("#936"),
    amber("#D65"),
    gold1("#FD4"),
    gold2("#F90"),
    white("#FFF"),
    rain("#5DE"),
    dust("#9AD"),
    wind("#BDD"),
    brown("#9AD"),

    gray1("#DDD"),
    gray2("#BBB"),
    gray3("#AAB"),
    gray4("#99A"),
    gray5("#889"),
    gray6("#778"),

    midday("#5AE"),

    blue1("#7AC"),
    blue2("#9AB"),
    blue3("#AAA"),
    blue4("#579"),
    blue5("#469"),
    blue6("#26A"),

    green1("#BF5"),
    green2("#7F0"),
    green3("#6D1"),
    black1("#544"),
    black2("#444"),
    black3("#334"),

    shader("#60222222");

    val colour = color.color
}

class PathKeyframes(path: Path, error: Float) : Keyframes {
    private val mTempPointF = PointF()
    private val mKeyframeData: FloatArray

    constructor(path: Path) : this(path, 0.5f) {}

    init {
        require(!path.isEmpty) { "The path must not be null or empty" }
        mKeyframeData = path.approximate(error)
    }

    override val keyframes: ArrayList<Keyframe>
        get() = EMPTY_KEYFRAMES

    override fun getValue(fraction: Float): Any {
        val numPoints = mKeyframeData.size / 3
        return if (fraction < 0) {
            interpolateInRange(fraction, 0, 1)
        } else if (fraction > 1) {
            interpolateInRange(fraction, numPoints - 2, numPoints - 1)
        } else if (fraction == 0f) {
            pointForIndex(0)
        } else if (fraction == 1f) {
            pointForIndex(numPoints - 1)
        } else {
            // Binary search for the correct section
            var low = 0
            var high = numPoints - 1
            while (low <= high) {
                val mid = (low + high) / 2
                val midFraction = mKeyframeData[mid * NUM_COMPONENTS + FRACTION_OFFSET]
                if (fraction < midFraction) {
                    high = mid - 1
                } else if (fraction > midFraction) {
                    low = mid + 1
                } else {
                    return pointForIndex(mid)
                }
            }

            // now high is below the fraction and low is above the fraction
            interpolateInRange(fraction, high, low)
        }
    }

    private fun interpolateInRange(fraction: Float, startIndex: Int, endIndex: Int): PointF {
        val startBase = startIndex * NUM_COMPONENTS
        val endBase = endIndex * NUM_COMPONENTS
        val startFraction = mKeyframeData[startBase + FRACTION_OFFSET]
        val endFraction = mKeyframeData[endBase + FRACTION_OFFSET]
        val intervalFraction = (fraction - startFraction) / (endFraction - startFraction)
        val startX = mKeyframeData[startBase + X_OFFSET]
        val endX = mKeyframeData[endBase + X_OFFSET]
        val startY = mKeyframeData[startBase + Y_OFFSET]
        val endY = mKeyframeData[endBase + Y_OFFSET]
        val x = interpolate(intervalFraction, startX, endX)
        val y = interpolate(intervalFraction, startY, endY)
        mTempPointF[x] = y
        return mTempPointF
    }

    override fun setEvaluator(evaluator: TypeEvaluator<*>) {}
    override val type: Class<*>
        get() = PointF::class.java

    override fun clone(): Keyframes {
        return this
    }

    private fun pointForIndex(index: Int): PointF {
        val base = index * NUM_COMPONENTS
        val xOffset = base + X_OFFSET
        val yOffset = base + Y_OFFSET
        mTempPointF[mKeyframeData[xOffset]] = mKeyframeData[yOffset]
        return mTempPointF
    }

    /**
     * Returns a FloatKeyframes for the X component of the Path.
     * @return a FloatKeyframes for the X component of the Path.
     */
    fun createXFloatKeyframes(): Keyframes.FloatKeyframes {
        return object : FloatKeyframesBase() {
            override val keyframes: ArrayList<Keyframe>
                get() = EMPTY_KEYFRAMES
            override fun getFloatValue(fraction: Float): Float {
                val pointF = this@PathKeyframes.getValue(fraction) as PointF
                return pointF.x
            }
        }
    }

    /**
     * Returns a FloatKeyframes for the Y component of the Path.
     * @return a FloatKeyframes for the Y component of the Path.
     */
    fun createYFloatKeyframes(): Keyframes.FloatKeyframes {
        return object : FloatKeyframesBase() {
            override val keyframes: ArrayList<Keyframe>
                get() = EMPTY_KEYFRAMES
            override fun getFloatValue(fraction: Float): Float {
                val pointF = this@PathKeyframes.getValue(fraction) as PointF
                return pointF.y
            }
        }
    }

    /**
     * Returns an IntKeyframes for the X component of the Path.
     * @return an IntKeyframes for the X component of the Path.
     */
    fun createXIntKeyframes(): Keyframes.IntKeyframes {
        return object : IntKeyframesBase() {
            override val keyframes: ArrayList<Keyframe>
                get() = EMPTY_KEYFRAMES
            override fun getIntValue(fraction: Float): Int {
                val pointF = this@PathKeyframes.getValue(fraction) as PointF
                return pointF.x.roundToInt()
            }
        }
    }

    /**
     * Returns an IntKeyframeSet for the Y component of the Path.
     * @return an IntKeyframeSet for the Y component of the Path.
     */
    fun createYIntKeyframes(): Keyframes.IntKeyframes {
        return object : IntKeyframesBase() {
            override val keyframes: ArrayList<Keyframe>
                get() = EMPTY_KEYFRAMES
            override fun getIntValue(fraction: Float): Int {
                val pointF = this@PathKeyframes.getValue(fraction) as PointF
                return pointF.y.roundToInt()
            }
        }
    }

    abstract class SimpleKeyframes : Keyframes {

        override fun setEvaluator(evaluator: TypeEvaluator<*>) {}

        override fun clone(): Keyframes {
            return this
        }
    }

    internal abstract class IntKeyframesBase : SimpleKeyframes(),
        Keyframes.IntKeyframes {
        override val type: Class<*>
            get() = Int::class.java

        override fun getValue(fraction: Float): Any {
            return getIntValue(fraction)
        }
    }

    internal abstract class FloatKeyframesBase : SimpleKeyframes(), Keyframes.FloatKeyframes {
        override val type: Class<*>
            get() = Float::class.java

        override fun getValue(fraction: Float): Any {
            return getFloatValue(fraction)
        }
    }

    companion object {
        private const val FRACTION_OFFSET = 0
        private const val X_OFFSET = 1
        private const val Y_OFFSET = 2
        private const val NUM_COMPONENTS = 3
        val EMPTY_KEYFRAMES = ArrayList<Keyframe>()

        private fun interpolate(fraction: Float, startValue: Float, endValue: Float): Float {
            val diff = endValue - startValue
            return startValue + diff * fraction
        }
    }
}

interface Keyframes : Cloneable {
    /**
     * Sets the TypeEvaluator to be used when calculating animated values. This object
     * is required only for Keyframes that are not either IntKeyframes or FloatKeyframes,
     * both of which assume their own evaluator to speed up calculations with those primitive
     * types.
     *
     * @param evaluator The TypeEvaluator to be used to calculate animated values.
     */
    fun setEvaluator(evaluator: TypeEvaluator<*>)

    /**
     * @return The value type contained by the contained Keyframes.
     */
    val type: Class<*>

    /**
     * Gets the animated value, given the elapsed fraction of the animation (interpolated by the
     * animation's interpolator) and the evaluator used to calculate in-between values. This
     * function maps the input fraction to the appropriate keyframe interval and a fraction
     * between them and returns the interpolated value. Note that the input fraction may fall
     * outside the [0-1] bounds, if the animation's interpolator made that happen (e.g., a
     * spring interpolation that might send the fraction past 1.0). We handle this situation by
     * just using the two keyframes at the appropriate end when the value is outside those bounds.
     *
     * @param fraction The elapsed fraction of the animation
     * @return The animated value.
     */
    fun getValue(fraction: Float): Any

    /**
     * @return A list of all Keyframes contained by this. This may return null if this is
     * not made up of Keyframes.
     */
    val keyframes: List<Keyframe>

    override fun clone(): Keyframes

    /**
     * A specialization of Keyframes that has integer primitive value calculation.
     */
    interface IntKeyframes : Keyframes {
        /**
         * Works like [.getValue], but returning a primitive.
         *
         * @param fraction The elapsed fraction of the animation
         * @return The animated value.
         */
        fun getIntValue(fraction: Float): Int
    }

    /**
     * A specialization of Keyframes that has float primitive value calculation.
     */
    interface FloatKeyframes : Keyframes {
        /**
         * Works like [.getValue], but returning a primitive.
         *
         * @param fraction The elapsed fraction of the animation
         * @return The animated value.
         */
        fun getFloatValue(fraction: Float): Float
    }
}